﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.IO;

using Gurucore.Framework.Core;
using Gurucore.Framework.Core.JITGeneration;

using Ader.TemplateEngine;

namespace Gurucore.Framework.DataAccess.Persistence
{
	public class TransformerClassGenerator
	{
		private Type m_oDTOType;		

		public TransformerClassGenerator(Type p_oDTOType)
		{
			m_oDTOType = p_oDTOType;
		}

		public DTOTransformer GetTransformer()
		{
			JITClassManager oJITClassMgr = Application.GetInstance().GetGlobalSharedObject<JITClassManager>();
			string sClassName = GetClassName();
			string sGeneratedCode = string.Empty;

			if (!oJITClassMgr.IsRegistered(sClassName))
			{
				sGeneratedCode = this.GenerateCode();
				System.Diagnostics.Debug.Write(sGeneratedCode);
			}
			Type oType = oJITClassMgr.GetRegisteredClass(sClassName, sGeneratedCode, true);

			return (DTOTransformer)Activator.CreateInstance(oType);
		}

		private string GetClassName()
		{
			ComplexTypeInfoManager complexTypeInfoMgr = Application.GetInstance().GetGlobalSharedObject<ComplexTypeInfoManager>();

			if (complexTypeInfoMgr.IsComplexType(m_oDTOType))
			{
				string sName = string.Empty;
				ComplexTypeInfo cplTypeInfo = complexTypeInfoMgr.GetComplexTypeInfo(m_oDTOType);
				foreach (PropertyInfo element in cplTypeInfo.Elements)
				{
					sName += "_" + element.PropertyType.Name;
				}

				return cplTypeInfo.Elements[0].PropertyType.Namespace + "." + sName + "_Transformer";
			}
			else
			{
				return m_oDTOType.FullName + "_Transformer";
			}
		}

		private string GenerateCode()
		{
			Dictionary<string, string> m_dicNullableMap = new Dictionary<string, string>();
			m_dicNullableMap.Add("Byte", "byte");
			m_dicNullableMap.Add("SByte", "sbyte");
			m_dicNullableMap.Add("Int32", "int");
			m_dicNullableMap.Add("UInt32", "uint");
			m_dicNullableMap.Add("Int16", "short");
			m_dicNullableMap.Add("UInt16", "ushort");
			m_dicNullableMap.Add("Int64", "long");
			m_dicNullableMap.Add("UInt64", "ulong");
			m_dicNullableMap.Add("Single", "float");
			m_dicNullableMap.Add("Double", "double");
			m_dicNullableMap.Add("Char", "char");
			m_dicNullableMap.Add("Boolean", "bool");
			m_dicNullableMap.Add("Decimal", "decimal");

			TemplateManager oTplMgr = null;

			//check if is complex type
			TableInfoManager oTableInfoMgr = Application.GetInstance().GetGlobalSharedObject<TableInfoManager>();
			ComplexTypeInfoManager complexTypeInfoMgr = Application.GetInstance().GetGlobalSharedObject<ComplexTypeInfoManager>();

			if (complexTypeInfoMgr.IsComplexType(m_oDTOType))
			{
				TextReader oReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Gurucore.Framework.DataAccess.Persistence.ComplexDTOTransformerTemplate.tpl"));
				string sTemplate = oReader.ReadToEnd();
				oReader.Close();

				oTplMgr = TemplateManager.FromString(sTemplate);

				ComplexTypeInfo cplTypeInfo = complexTypeInfoMgr.GetComplexTypeInfo(m_oDTOType);

				if (cplTypeInfo.Elements.Count == 0)
				{
					//TODO: classify this exception
					throw new Exception();
				}
				DTOComplexInfo complex = new DTOComplexInfo();
				complex.Namespace = cplTypeInfo.Elements[0].PropertyType.Namespace;

				List<DTOClassInfo> elements = new List<DTOClassInfo>();
				foreach (PropertyInfo element in cplTypeInfo.Elements)
				{
					complex.Name += "_" + element.PropertyType.Name;

					TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(element.PropertyType);

					DTOClassInfo elementClass = new DTOClassInfo();
					elementClass.Name = element.PropertyType.Name;
					elementClass.Namespace = element.PropertyType.Namespace;
					List<DTOPropertyInfo> arrProps = new List<DTOPropertyInfo>();
					foreach (string sColumn in oTableInfo.Column)
					{
						string sPropertyType = null;
						if (oTableInfo.Property[sColumn].PropertyType.IsGenericType)
						{
							//Nullable
							sPropertyType = m_dicNullableMap[oTableInfo.Property[sColumn].PropertyType.GetGenericArguments()[0].Name];
						}
						else
						{
							sPropertyType = oTableInfo.Property[sColumn].PropertyType.Name;
						}
						DTOPropertyInfo oProp = new DTOPropertyInfo();
						oProp.Name = oTableInfo.Property[sColumn].Name;
						oProp.FieldName = oTableInfo.TableName + "." + oProp.Name; //note: not use table prefix, because it may be different at runtime
						oProp.Type = sPropertyType;
						arrProps.Add(oProp);
					}
					elementClass.Properties = arrProps.ToArray();

					elements.Add(elementClass);
				}
				complex.Elements = elements.ToArray();
				oTplMgr.SetValue("complex", complex);
			}
			else
			{
				TextReader oReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Gurucore.Framework.DataAccess.Persistence.SingleDTOTransformerTemplate.tpl"));
				string sTemplate = oReader.ReadToEnd();
				oReader.Close();

				oTplMgr = TemplateManager.FromString(sTemplate);

				TableInfo oTableInfo = oTableInfoMgr.GetTableInfo(m_oDTOType);

				DTOClassInfo oClass = new DTOClassInfo();
				oClass.Namespace = m_oDTOType.Namespace;
				oClass.Name = m_oDTOType.Name;

				List<DTOPropertyInfo> arrProps = new List<DTOPropertyInfo>();
				foreach (string sColumn in oTableInfo.Column)
				{
					string sPropertyType = null;
					if (oTableInfo.Property[sColumn].PropertyType.IsGenericType)
					{
						//Nullable
						sPropertyType = m_dicNullableMap[oTableInfo.Property[sColumn].PropertyType.GetGenericArguments()[0].Name];
					}
					else
					{
						sPropertyType = oTableInfo.Property[sColumn].PropertyType.Name;
					}
					DTOPropertyInfo oProp = new DTOPropertyInfo();
					oProp.Name = oTableInfo.Property[sColumn].Name;
					oProp.FieldName = oTableInfo.TableName + "." + oProp.Name; //note: not use table prefix, because it may be different at runtime
					oProp.Type = sPropertyType;
					arrProps.Add(oProp);
				}
				oClass.Properties = arrProps.ToArray();

				oTplMgr.SetValue("class", oClass);
				//oTplMgr.SetValue("properties", arrProps.ToArray());
			}

			string sGeneratedCode = oTplMgr.Process();

			return sGeneratedCode;
		}

		private struct DTOComplexInfo
		{
			public string Namespace { get; set; }
			public string Name { get; set; }

			public DTOClassInfo[] Elements { get; set; }
		}

		private struct DTOClassInfo
		{
			private string m_sNamespace;

			public string Namespace
			{
				get { return m_sNamespace; }
				set { m_sNamespace = value; }
			}

			private string m_sName;

			public string Name
			{
				get { return m_sName; }
				set { m_sName = value; }
			}

			public DTOPropertyInfo[] Properties { get; set; }
		}

		private struct DTOPropertyInfo
		{
			private string m_sName;

			public string Name
			{
				get { return m_sName; }
				set { m_sName = value; }
			}

			private string m_sType;

			public string Type
			{
				get { return m_sType; }
				set { m_sType = value; }
			}

			public string FieldName { get; set; }
		}
	}
}